package com.edol.painting.widget;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.Shader.TileMode;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.DecelerateInterpolator;

import com.doodle.common.drawable.MeasuredAsyncDrawable;
import com.doodle.common.utils.BitmapPool;
import com.doodle.common.utils.BitmapUtils;
import com.doodle.common.utils.GestureRecognizer;
import com.doodle.common.utils.ThreadPool;
import com.doodle.common.utils.ThreadPool.Job;
import com.doodle.common.utils.ThreadPool.JobContext;
import com.doodle.common.utils.Utils;
import com.edol.painting.R;
import com.edol.painting.painting.Action;
import com.edol.painting.painting.Brush;
import com.edol.painting.painting.EraserBrush;
import com.edol.painting.painting.KeyFrame;
import com.edol.painting.painting.Particle;
import com.edol.painting.painting.Route;
import com.edol.painting.painting.Theme;
import com.edol.painting.utils.ChangeBgAnim;
import com.edol.painting.utils.Constant;
import com.edol.painting.utils.PainterUtils;
import com.edol.painting.utils.ViewerGestureListener;
import com.edol.painting.utils.ViewerGestureListener.ViewRectChangedListener;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;

public class PaintView extends View implements ViewRectChangedListener {
    private final int COMMIT_INSERT_PICTURE = 0;
    private final int COMMIT_FILL_TEXTURE = 1;
    private final int COMMIT_CUT_BITMAP = 2;

    private Bitmap mPaper;
    private int mEraserColor;
    private int mEraserWidth = 0;
    private RectF mEraserRect;
    private Paint mEraserPaint;
    private Bitmap mBrushLayer;
    private Canvas mBrushLayerCanvas;
    private Brush mBrush;
    private Brush mPlaybackBrush;
    private VelocityTracker mVelocityTracker;
    private Route mCurrentRoute;
    private Rect mBounds;
    private RectF mBoundsF;
    private Paint mPaintingPaint;
    private Paint mBrushAlphaPaint;
    private Paint mBitmapShadowPaint;

    private boolean mPaintingLayers = true;
    private int mOldTheme;
    private int mTheme;

    private boolean mShowEyeDropperView = false;
    private boolean mShowEyeDropperWhenTouch = false;
    private boolean mEyeDropColorOK = false;

    private Bitmap mEyeDropperBitmap;
    private Canvas mEyeDropperCanvas;
    private Paint mEyeDropperPaint;
    private Paint mEyeDropperSrcInPaint;
    private Paint mEyeRingPaint;
    private Paint mEyeCirclePaint;

    private Rect mEyeDropperSrcRect;
    private RectF mEyeDropperDstRect;
    private RectF mEyeDropperBitmapRect;
    private RectF mEyeDropperDstBitmapRect;

    private float mEyeDropperViewX;
    private float mEyeDropperViewY;
    private int mEyeDropperCurrentX;
    private int mEyeDropperCurrentY;
    private int mEyeDropperInOutSize;
    private int mEyeDropperInSize;

    private ValueAnimator mAnimator;
    private float mStartRotate;
    private float mEndRotate;
    private float mAngle;
    private float mRotate;

    private boolean mInCut = false;
    private boolean mInCutCommit = false;
    private boolean mInCutBitmapMove = false;
    private Paint mCutPaint;
    private Paint mCutFillPaint;
    private RectF mCutPathRect;
    private RectF mCutOriginRect;
    private float mCutLastX;
    private float mCutLastY;
    private float mCutCurrentX = 0;
    private float mCutCurrentY = 0;
    private DashPathEffect mEffect;
    private DashPathEffect mCutEffect;
    private int mPhase = 0;

    private int mEyeDropperColor;

    private Bitmap mMagnifier;

    private OnEyeDropperChangedListener mEyeDropperChangedListener;

    private float mLastX;
    private float mLastY;
    private float mLastAngle;
    private static final int TOUCH_TOLERANCE = 4;

    private int mForceWidth = Constant.PAINTING_WIDTH;
    private int mForceHeight = Constant.PAINTING_HEIGHT;
    private int mBitmapWidth = Constant.PAINTING_WIDTH;
    private int mBitmapHeight = Constant.PAINTING_HEIGHT;
    private RectF mPaintViewRect;

    private ViewerGestureListener mGestureListener;
    private GestureRecognizer mGestureRecognizer;

    private int mBrushType;
    private int mBrushWidth;
    private int mBrushColor;
    private int mBrushAlpha;

    private static final int DRAW_LIMIT_TIME = 600;

    private int mTouchState = -2;
    private float mTouchDownX = 0;
    private float mTouchDownY = 0;
    private float mTouchDownAngle = 0;
    private int mTouchSlope = 4;

    private ColorPickerView mColorPicker;

    private PaintStateListener mPaintStateListener;

    private KeyFrameManager mKeyFrameManager;

    private int mSavedRoutId = -1;

    private Bitmap mBitmapBackup;
    private List<Route> mRoutes;
    private List<Action> mActions;
    private List<Action> mTempActions;
    private int mCurrentRouteId = -1;
    private int mCurrentActionID = 0;

    private boolean mPlaybackPending = false;

    private Rect mEyeDropCancelRect = null;

    private RectF mTexturePathRect;
    private Bitmap mTextureOrCutBitmap;
    private Canvas mTextureOrCutCanvas;
    private Region mRectRegion;
    private Region mChoiceRegion;

    public interface OnEyeDropperChangedListener {
        void onEyeDropperStart();

        void onEyeDropperEnd(int color, boolean cancel);

        void onEyeDropperColorChanged(int color, boolean cancel);
    }

    public PaintView(Context context) {
        super(context);
        initializePaintView(context);
    }

    public PaintView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initializePaintView(context);
    }

    public PaintView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initializePaintView(context);
    }

    @Override
    protected void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }
        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());
        mRoutes.addAll(ss.routes);
        mActions.addAll(ss.actions);
        mTempActions.addAll(ss.tempActions);
        mKeyFrameManager.mKeyFrames.addAll(ss.keyFrames);
        mCurrentRouteId = ss.currentRouteId;
        mCurrentActionID = ss.currentActionID;
        mPlaybackPending = true;
    }

    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.routes = mRoutes;
        ss.actions = mActions;
        ss.tempActions = mTempActions;
        ss.keyFrames = mKeyFrameManager.mKeyFrames;
        ss.currentRouteId = mCurrentRouteId;
        ss.currentActionID = mCurrentActionID;
        return ss;
    }

    public interface PaintStateListener {
        void onRouteStateChanged();

        void onSingleTapUp();

        void onDownPaintView();

        void onEditUp();

        void onTouchDown();

        void onTouchUp();

        void onInsertFinish();
    }

    public void releaseData() {

    }

    public boolean getInCutCommit() {
        return mInCutCommit;
    }

    public boolean getInTextureCommit() {
        return mFillTextureCommit;
    }

    private void initializePaintView(Context context) {
        setFocusable(true);
        mRoutes = new ArrayList<Route>();
        mCurrentRoute = null;
        mBounds = new Rect();
        mBoundsF = new RectF();
        mPaintViewRect = new RectF(0, 0, mBitmapWidth, mBitmapHeight);

        mBitmapShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitmapShadowPaint.setAntiAlias(true);
        mBitmapShadowPaint.setStyle(Style.STROKE);
        mBitmapShadowPaint.setColor(getResources().getColor(R.color.painter_actionbar_color_line));
        mBitmapShadowPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.actionbar_line));


        mEyeDropperPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEyeDropperPaint.setAntiAlias(true);
        mEyeDropperPaint.setColor(0xff424242);

        mEyeRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEyeRingPaint.setStyle(Style.FILL);
        mEyeRingPaint.setColor(Color.GREEN);

        mEyeCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEyeCirclePaint.setAntiAlias(true);
        mEyeCirclePaint.setStyle(Style.STROKE);

        mEyeDropperSrcInPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mEyeDropperSrcInPaint.setAntiAlias(true);
        mEyeDropperSrcInPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
        mEyeCirclePaint.setColor(getResources().getColor(R.color.eyecircle_paint_size_color));

        mPaintingPaint = new Paint();
        mPaintingPaint.setFilterBitmap(true);
        mPaintingPaint.setFlags(Paint.ANTI_ALIAS_FLAG);

        mBrushAlpha = 200;
        mBrushAlphaPaint = new Paint();
        mBrushAlphaPaint.setAlpha(mBrushAlpha);
        mBrushAlphaPaint.setFilterBitmap(true);

        mForceWidth = Constant.PAINTING_WIDTH;
        mForceHeight = Constant.PAINTING_HEIGHT;

        mEyeDropperInOutSize = (int) getResources().getDimensionPixelSize(R.dimen.eyecircle_in_out_size);
        mEyeDropperInSize = (int) getResources().getDimensionPixelSize(R.dimen.eyecircle_in_size);

        mEraserPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEraserPaint.setAntiAlias(true);
        mEraserPaint.setStyle(Style.STROKE);
        mEraserPaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.actionbar_line));

		/*mAnimator = ObjectAnimator.ofFloat(this, "eyeDropperAngle", 0, 1.0f);
		mAnimator.setDuration(300);
		mAnimator.setInterpolator(new DecelerateInterpolator());
		
		mFillBounds = new RectF();
		mBrushLayerRect = new Rect();*/

        mCutPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mCutFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mEffect = new DashPathEffect(new float[]{10, 10}, 0);

        mGestureListener = new ViewerGestureListener(mForceWidth, mForceHeight, this) {
            @Override
            public boolean onSingleTapUp(float x, float y) {
                if (mPaintStateListener != null) {
                    if (!mInInsertImage) {
                        if (mIsDeleteOp) {
                            mIsDeleteOp = false;
                        } else {
                            if (!mInCutCommit && !mFillTextureCommit) {
                                if (mTouchState != 1) {
                                    mPaintStateListener.onSingleTapUp();
                                } else {
                                    mTouchState = -1;
                                }
                            } else if (!mInCutBitmapMove && mInCut) {
                                mInCutCommit = false;
                                commitBitmap(COMMIT_CUT_BITMAP);
                            } else if (!mInTextureBitmapMove && mInFillTexture) {
                                mFillTextureCommit = false;
                                commitBitmap(COMMIT_FILL_TEXTURE);
                            }
                        }
                    } else if (!mChoiceRegion.contains((int) x, (int) y)) {
                        commitInsert();
                    }
                }
                return false;
            }

            @Override
            public void onLongPress(float x, float y) {
                if (!mShowEyeDropperView && mTouchState != 1) {
                    if (mEyeDropCancelRect == null) {
                        mEyeDropCancelRect = new Rect(0, Constant.PAINTING_HEIGHT, Constant.PAINTING_WIDTH, Constant.PAINTING_HEIGHT);
                    }
                    if (mEyeDropperDstBitmapRect == null) {
                        mEyeDropperDstBitmapRect = new RectF();
                    }
                    if (mEyeDropperSrcRect == null) {
                        mEyeDropperSrcRect = new Rect();
                    }
                    if (mEyeDropperDstRect == null) {
                        mEyeDropperDstRect = new RectF();
                    }
                    if (mEyeDropperBitmapRect == null) {
                        mEyeDropperBitmapRect = new RectF(0, 0, Constant.EYEDROPPER_SRC_WIDTH, Constant.EYEDROPPER_SRC_WIDTH);
                    }
                    if (mEyeDropperBitmap == null) {
                        mEyeDropperBitmap = Bitmap.createBitmap(Constant.EYEDROPPER_SRC_WIDTH, Constant.EYEDROPPER_SRC_WIDTH, Config.ARGB_8888);
                        mEyeDropperCanvas = new Canvas(mEyeDropperBitmap);
                    }
                    if (mMagnifier == null) {
                        mMagnifier = BitmapFactory.decodeResource(getResources(), R.drawable.eyedropper_center);
                    }
                    if (mAnimator == null) {
                        mAnimator = ObjectAnimator.ofFloat(this, "eyeDropperAngle", 0, 1.0f);
                        mAnimator.setDuration(300);
                        mAnimator.setInterpolator(new DecelerateInterpolator());
                    }

                    mShowEyeDropperView = true;
                    setEyeDropperPosition(x, y, false);
                    if (mEyeDropperChangedListener != null) {
                        mEyeDropperChangedListener.onEyeDropperStart();
                    }
                    invalidate();
                }
            }

            @Override
            public void onUp() {
                if (mShowEyeDropperView) {
                    mShowEyeDropperView = false;
                    if (mEyeDropColorOK && mColorPicker != null) {
                        mColorPicker.setCurrentColor(mEyeDropperColor, true);
                        mColorPicker.invalidate();
                    }
                    if (mEyeDropperChangedListener != null) {
                        mEyeDropperChangedListener.onEyeDropperEnd(mEyeDropperColor, mEyeDropColorOK);
                        mAnimator.cancel();
                        mEyeDropperBitmap.recycle();
                        mEyeDropperBitmap = null;
                    }
                }
            }
        };
        mGestureListener.setViewCenter((float) mForceWidth / 2, (float) (mForceHeight) / 2);
        mGestureRecognizer = new GestureRecognizer(context, mGestureListener);
        mActions = new ArrayList<Action>();
        mTempActions = new ArrayList<Action>();

        mKeyFrameManager = new KeyFrameManager();

        mTouchSlope = ViewConfiguration.get(getContext()).getScaledTouchSlop();
    }

    private Bitmap mMaskLayer = null;
    private Canvas mMaskLayerCanvas = null;
    private Bitmap mPaintBitmap;
    private Canvas mPaintCanvas;

    public void setPainting(int newTheme, int oldTheme, Bitmap paintingBrush, boolean destroy) {
        mPaintBitmap = getPaintBitmap(Constant.PAINTING_WIDTH, Constant.PAINTING_HEIGHT);
        mPaintCanvas = new Canvas(mPaintBitmap);

        mEraserColor = (newTheme < 0 || newTheme > Theme.THEME_ALL - 1) ? newTheme : 0xff000000;
        setPainting(newTheme);
        mOldTheme = oldTheme;

        if (mMaskLayer == null) {
            mMaskLayer = getMaskLayerBitmap(mBitmapWidth, mBitmapHeight);
            mMaskLayerCanvas = new Canvas(mMaskLayer);
            if (!destroy) {
                mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
                if (mPaintMode == PAINT_MODE_EDIT) {
                    mMaskLayerCanvas.drawBitmap(paintingBrush, null, mPaintViewRect, null);
                }
            }
        }

        if (mPaper != null) {
            BitmapShader shader = new BitmapShader(mPaper, TileMode.REPEAT, TileMode.REPEAT);
            mBgPaint.reset();
            mBgPaint.setStyle(Style.FILL);
            mBgPaint.setShader(shader);
        } else {
            mBgPaint.reset();
            mBgPaint.setStyle(Style.FILL);
            mBgPaint.setColor(newTheme);
        }

        if (mBrushLayer == null) {
            mBrushLayer = getBrushLayerBitmap(mBitmapWidth, mBitmapHeight);
            mBrushLayerCanvas = new Canvas(mBrushLayer);
            mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
        }

        updateDrawing();

        if (mMaskLayer != null && mMaskLayer.isMutable()) {
            mBitmapBackup = getBackupBitmap(mMaskLayer.getWidth(), mMaskLayer.getHeight());
            if (!destroy) {
                Canvas c = new Canvas(mBitmapBackup);
                c.drawColor(0x0, Mode.CLEAR);
                c.drawBitmap(mMaskLayer, 0, 0, null);
            }
        }
    }

    public void setPainting(Bitmap paintingBrush, boolean layers) {
        if (mMaskLayer == null) {
            mPaintingLayers = layers;
            mMaskLayer = getMaskLayerBitmap(mBitmapWidth, mBitmapHeight);
            mMaskLayerCanvas = new Canvas(mMaskLayer);
            mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
            mMaskLayerCanvas.drawBitmap(paintingBrush, null, mPaintViewRect, null);
        }
    }

    public void updateDrawing() {
        mPaintCanvas.drawColor(0x0, Mode.CLEAR);
        mPaintCanvas.drawRect(0, 0, mPaintBitmap.getWidth(), mPaintBitmap.getHeight(), mBgPaint);
        mPaintCanvas.drawBitmap(mMaskLayer, 0, 0, null);
        invalidate();
    }

    public void redoBitmap() {
        mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
        mMaskLayerCanvas.drawBitmap(mBitmapBackup, 0, 0, null);
        updateDrawing();
    }

    public void setPainting(int theme) {
        mTheme = theme;
        if (theme < 0 || theme > Theme.THEME_ALL - 1) {
            mPaper = null;
            mEraserColor = theme;
        } else {
            mPaper = Theme.THEME_LIST[theme].getPaper(getContext());
        }
    }

    public void setShowEyeDropperView(boolean showEyeDropperView) {
        mShowEyeDropperView = showEyeDropperView;
        mShowEyeDropperWhenTouch = showEyeDropperView;
    }

    public void checkPendingPlayback() {
        if (mPlaybackPending) {
            mPlaybackPending = false;
            if (mMaskLayer != null) {
                playback();
            }
        }
    }

    public Bitmap getPainting() {
        return mPaintBitmap;
    }

    public Bitmap getMaskLayer() {
        return mMaskLayer;
    }

    public void updateAction(int type, int value) {
        int actionCount = mActions.size();
        if (mCurrentActionID < actionCount - 1) {
            if (type == Action.ACTION_TYPE_DRAW) {
                for (int i = actionCount - 1; i > mCurrentActionID; --i) {
                    mActions.remove(i);
                }
                for (int i = 0, j = mTempActions.size(); i < j; ++i) {
                    Action action = mTempActions.get(i);
                    checkAction(action.mType, action.mValue);
                }
                mTempActions.clear();
            } else {
                if (type == Action.ACTION_TYPE_CHANGE_BRUSH) {
                    mTempActions.clear();
                }
                addAction(type, value, mTempActions);
                return;
            }
        }

        addAction(type, value, mActions);
        mCurrentActionID = mActions.size() - 1;
    }

    private static void addAction(int type, int value, List<Action> actions) {
        int actionCount = actions.size();
        for (int i = actionCount - 1; i >= 0; --i) {
            Action action = actions.get(i);
            if (action.mType == Action.ACTION_TYPE_DRAW) {
                if (type == Action.ACTION_TYPE_CHANGE_BRUSH) {
                    for (int j = actionCount - 1; j > i; --j) {
                        actions.remove(j);
                    }
                }
                break;
            } else if (action.mType == Action.ACTION_TYPE_CHANGE_BRUSH) {
                if (type != Action.ACTION_TYPE_CHANGE_BRUSH) {
                    break;
                }
            } else if (action.mType == type) {
                action.mValue = value;
                return;
            }
        }
        actions.add(new Action(type, value));
    }

    public void checkAction(int type, int value) {
        int actionCount = mActions.size();
        for (int i = actionCount - 1; i >= 0; i--) {
            Action action = mActions.get(i);
            if (action.mType == type) {
                if (action.mValue != value) {
                    mActions.add(new Action(type, value));
                }
                return;
            }
        }
        mActions.add(new Action(type, value));
    }

    public void setBrush(int brushType) {
        mBrush = Brush.getBrush(getContext(), Brush.TARGET_PAINT, brushType);
        if (mBrush != null) {
            mBrushType = brushType;
            mBrush.setBrushLayer(mBrushLayer, mBrushLayerCanvas);
            if (mBrush instanceof EraserBrush) {
                mBrush.setPaper(mPaper);
                mBrush.setColor(mEraserColor);
                if (mPaper == null) {
                    updateAction(Action.ACTION_TYPE_CHANGE_COLOR, mEraserColor);
                }
            }
            updateAction(Action.ACTION_TYPE_CHANGE_BRUSH, brushType);
        }
    }

    public void setOnEyeDropperChangedListener(OnEyeDropperChangedListener l) {
        mEyeDropperChangedListener = l;
    }

    public Brush getBrush() {
        return mBrush;
    }

    public int getBrushType() {
        return mBrushType;
    }

    public void setBrushWidth(int brushWidth) {
        if (mBrush != null) {
            mBrushWidth = brushWidth;
            mBrush.setWidth(brushWidth);
            updateAction(Action.ACTION_TYPE_CHANGE_WIDTH, brushWidth);
            if (mBrush instanceof EraserBrush) {
                mEraserWidth = brushWidth;
                if (mEraserRect == null) {
                    mEraserRect = new RectF();
                }
            }
        }
    }

    public int getBrushWidth() {
        return mBrushWidth;
    }

    public void setBrushColor(int brushColor) {
        if (mBrush != null) {
            mBrushColor = brushColor;
            mBrush.setColor(brushColor);
            updateAction(Action.ACTION_TYPE_CHANGE_COLOR, brushColor);
        }
    }

    public void setBrushAlpha(int brushAlpha) {
        if (mBrush != null) {
            mBrushAlpha = PainterUtils.getAlphaCurveValue(brushAlpha);
            mBrushAlphaPaint.setAlpha(mBrushAlpha);
            mBrush.setAlpha(mBrushAlpha);
            updateAction(Action.ACTION_TYPE_CHANGE_ALPHA, mBrushAlpha);
        }
    }

    public void setColorPicker(ColorPickerView colorPicker) {
        mColorPicker = colorPicker;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mPaintBitmap != null) {
            RectF dstRect = mGestureListener.getDstRect();
            if (mChangeBgCount < 0 || mChangeBgCount > (Constant.PAINTING_EDIT_CHANGE_BACKGROUND_FRAME - 1)) {
                canvas.drawBitmap(mPaintBitmap, null, dstRect, mPaintingPaint);
                if (mBrushLayer != null && !mBounds.isEmpty()) {
                    calcTransformedDstRect(dstRect);
                    canvas.drawBitmap(mBrushLayer, mBounds, mBoundsF, mBrushAlphaPaint);
                }
            } else {
                canvas.drawBitmap(mBrushLayer, null, dstRect, mPaintingPaint);
                mChangeBgAnim.start(mChangeBgCount++, canvas);
                canvas.drawBitmap(mMaskLayer, null, dstRect, null);
                if (mChangeBgCount > (Constant.PAINTING_EDIT_CHANGE_BACKGROUND_FRAME - 1)) {
                    mChangeBgAnim.clear();
                    mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
                }
                invalidate();
            }

            if (mInFillTexture && mTextureOrCutBitmap != null && !mInInsertImage) {
                canvas.drawBitmap(mTextureOrCutBitmap, null, mTexturePathRect, null);
            }
            if (mInFillTexture && !mInInsertImage) {
                if (!mFillTextureCommit) {
                    canvas.drawPath(mEdgePath, mEdgeUpPathPaint);
                    canvas.drawPath(mEdgePath, mEdgeDashPathPaint);
                } else {
                    mCutEffect = new DashPathEffect(new float[]{10, 10}, mPhase++);
                    mEdgeDashPathPaint.setPathEffect(mCutEffect);
                    canvas.drawPath(mEdgePath, mEdgeUpPathPaint);
                    canvas.drawPath(mEdgePath, mEdgeDashPathPaint);
                    invalidate();
                }
            }
            if (mInCut && mTextureOrCutBitmap != null && !mInInsertImage) {
                canvas.drawBitmap(mTextureOrCutBitmap, null, mCutPathRect, null);
            }
            if (mInCut && !mInInsertImage) {
                if (!mInCutCommit) {
                    canvas.drawPath(mCutEdgePath, mEdgeUpPathPaint);
                    canvas.drawPath(mCutEdgePath, mEdgeDashPathPaint);
                } else {
                    mCutEffect = new DashPathEffect(new float[]{10, 10}, mPhase++);
                    mEdgeDashPathPaint.setPathEffect(mCutEffect);
                    canvas.drawPath(mCutEdgePath, mEdgeUpPathPaint);
                    canvas.drawPath(mCutEdgePath, mEdgeDashPathPaint);
                    invalidate();
                }
            }
            if (mInInsertImage) {
                // draw inserted bitmap
                canvas.drawBitmap(mTextureOrCutBitmap, mInsertImageMatrix, null);
                // draw edge path
                canvas.drawPath(mEdgePath, mEdgeDownPathPaint);
                canvas.drawPath(mEdgePath, mEdgeUpPathPaint);
                // draw delete op bitmap
                canvas.drawBitmap(mInsertDeleteBitmap, null, mDeleteInsertRect, null);
                // draw scale op bitmap
                canvas.drawBitmap(mInsertScaleBitmap, null, mScaleInsertRect, null);
            }
            if (mTouchState == 1 && (mBrush instanceof EraserBrush) && !mInInsertImage && !mInFillTexture && !mInCut) {
                mEraserPaint.setColor(Color.WHITE);
                canvas.drawCircle(mEraserRect.left, mEraserRect.top, mEraserWidth / 2, mEraserPaint);
                mEraserPaint.setColor(Color.GRAY);
                canvas.drawCircle(mEraserRect.left, mEraserRect.top, mEraserWidth / 2 - mEraserPaint.getStrokeWidth(), mEraserPaint);
            }
            if (mShowEyeDropperView) {
                int srcHalfWidth = Constant.EYEDROPPER_SRC_WIDTH / 2;
                mEyeDropperSrcRect.set(mEyeDropperCurrentX - srcHalfWidth, mEyeDropperCurrentY - srcHalfWidth,
                        mEyeDropperCurrentX + srcHalfWidth, mEyeDropperCurrentY + srcHalfWidth);
                mEyeDropperCanvas.drawARGB(0, 0, 0, 0);
                mEyeDropperCanvas.drawCircle(srcHalfWidth, srcHalfWidth, srcHalfWidth, mEyeDropperPaint);
                mEyeDropperCanvas.drawBitmap(mPaintBitmap, mEyeDropperSrcRect, mEyeDropperBitmapRect, mEyeDropperSrcInPaint);

                float eyeDropperViewX = (float) (mEyeDropperViewX + Math.cos(Constant.PI * mRotate / 180) * Constant.EYEDROPPER_DST_OFFSET);
                float eyeDropperViewY = (float) (mEyeDropperViewY - Math.sin(Constant.PI * mRotate / 180) * Constant.EYEDROPPER_DST_OFFSET);

                float dstHalfWidth = mEyeDropperInSize / 2;
                float left = eyeDropperViewX - dstHalfWidth;
                if (left < Constant.EYEDROPPER_MAGNIFIER_SHADOW_WIDTH) {
                    left = Constant.EYEDROPPER_MAGNIFIER_SHADOW_WIDTH;
                }
                float top = eyeDropperViewY - dstHalfWidth;
                if (top < Constant.EYEDROPPER_MAGNIFIER_SHADOW_WIDTH) {
                    top = Constant.EYEDROPPER_MAGNIFIER_SHADOW_WIDTH;
                }
                float right = left + mEyeDropperInSize;
                if (right > getWidth() - Constant.EYEDROPPER_MAGNIFIER_SHADOW_WIDTH) {
                    right = getWidth() - Constant.EYEDROPPER_MAGNIFIER_SHADOW_WIDTH;
                    left = right - mEyeDropperInSize;
                }
                float bottom = top + mEyeDropperInSize;
                mEyeDropperDstRect.set(left, top, right, bottom);

                mEyeDropperDstBitmapRect.set(left + mEyeDropperInOutSize, top + mEyeDropperInOutSize,
                        right - mEyeDropperInOutSize, bottom - mEyeDropperInOutSize);
                canvas.save();
                canvas.clipRect(left, top, right - mEyeDropperDstRect.width() / 2, bottom);
                mEyeRingPaint.setColor(mEyeDropperColor);
                canvas.drawCircle(left + mEyeDropperDstRect.width() / 2, top + mEyeDropperDstRect.height() / 2, dstHalfWidth, mEyeRingPaint);
                canvas.restore();

                canvas.save();
                canvas.clipRect(left + mEyeDropperDstRect.width() / 2, top, right, bottom);
                mEyeRingPaint.setColor(mBrushColor);
                canvas.drawCircle(left + mEyeDropperDstRect.width() / 2, top + mEyeDropperDstRect.height() / 2, dstHalfWidth, mEyeRingPaint);
                canvas.restore();

                // draw outter white cicle
                mEyeCirclePaint.setStrokeWidth(getResources().getDimensionPixelSize(R.dimen.eyecircle_paint_size));
                mEyeCirclePaint.setColor(getResources().getColor(R.color.eyecircle_paint_size_color));
                canvas.drawCircle(left + mEyeDropperDstRect.width() / 2, top + mEyeDropperDstRect.height() / 2, dstHalfWidth, mEyeCirclePaint);

                float magnifierHalfW = mMagnifier.getWidth() / 2;
                canvas.drawBitmap(mEyeDropperBitmap, null, mEyeDropperDstBitmapRect, null);
                // draw inner white cicle
                canvas.drawCircle(left + mEyeDropperDstRect.width() / 2, top + mEyeDropperDstRect.height() / 2, mEyeDropperDstBitmapRect.width() / 2, mEyeCirclePaint);

                mEyeCirclePaint.setStrokeWidth(1);
                mEyeCirclePaint.setColor(getResources().getColor(R.color.eyecircle_paint_middle_color));
                canvas.drawCircle(left + mEyeDropperDstRect.width() / 2, top + mEyeDropperDstRect.height() / 2, dstHalfWidth, mEyeCirclePaint);
                canvas.drawCircle(left + mEyeDropperDstRect.width() / 2, top + mEyeDropperDstRect.height() / 2, mEyeDropperDstBitmapRect.width() / 2, mEyeCirclePaint);

                canvas.drawBitmap(mMagnifier, left + mEyeDropperDstRect.width() / 2 - magnifierHalfW, top + mEyeDropperDstRect.height() / 2 - magnifierHalfW, null);
            }
            canvas.drawRect(dstRect, mBitmapShadowPaint);
        }
    }

    private void calcTransformedDstRect(RectF dstRect) {
        float left = mGestureListener.transformX(mBounds.left);
        float top = mGestureListener.transformY(mBounds.top);
        float right = mGestureListener.transformX(mBounds.right);
        float bottom = mGestureListener.transformY(mBounds.bottom);
        left = Math.max(dstRect.left, left);
        top = Math.max(dstRect.top, top);
        right = Math.min(dstRect.right, right);
        bottom = Math.min(dstRect.bottom, bottom);
        mBoundsF.set(left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(mForceWidth, mForceHeight);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mBrush != null) {
            if (mChangeBgCount >= 0 && mChangeBgCount < Constant.PAINTING_EDIT_CHANGE_BACKGROUND_FRAME) {
                return true;
            }
            int action = event.getAction();
            if (action == MotionEvent.ACTION_DOWN
                    || action == MotionEvent.ACTION_UP
                    || action == MotionEvent.ACTION_CANCEL
                    || (mGestureRecognizer.isDown() && action == MotionEvent.ACTION_MOVE)) {
                acquireVelocityTracker(event);
                mVelocityTracker.computeCurrentVelocity(1);

                float x = event.getX();
                float y = event.getY();
                float angle = PainterUtils.getAngle(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
                if (mPaintStateListener != null) {
                    mPaintStateListener.onDownPaintView();
                }
                if (mBrush instanceof EraserBrush) {
                    mEraserRect.set(x, y, x, y);
                }
                switch (action) {
                    case MotionEvent.ACTION_DOWN:
                        mTouchState = -1;
                        mPaintStateListener.onTouchDown();
                        break;
                    case MotionEvent.ACTION_UP:
                        mPaintStateListener.onTouchUp();
                        break;
                }

                if (mShowEyeDropperView) {
                    if (mPaintBitmap != null) {
                        setEyeDropperPosition(x, y, !mShowEyeDropperWhenTouch);
                        if (mShowEyeDropperWhenTouch) {
                            mShowEyeDropperWhenTouch = false;
                        }
                    }
                } else if (mInInsertImage) {
                    setInsertImagePosition(x, y, action);
                } else if (mInFillTexture) {
                    if (!mFillTextureCommit && mTouchState == -1) {
                        calFillRegion(x, y, action);
                    } else {
                        moveTextureBitmap(x, y, action);
                    }
                } else if (mInCut) {
                    if (!mInCutCommit && mTouchState == -1) {
                        calFillRegion(x, y, action);
                    } else {
                        calCutRegion(x, y, action);
                    }
                } else {
                    if (action == MotionEvent.ACTION_DOWN) {
                        mTouchDownX = x;
                        mTouchDownY = y;
                        mTouchDownAngle = angle;

                        mTouchState = isCanvasRange(mGestureListener.inverseX(mTouchDownX), mGestureListener.inverseY(mTouchDownY)) ? 0 : -1;
                    } else if (action == MotionEvent.ACTION_MOVE) {
                        if (mTouchState == 0) {
                            if (Math.abs(x - mTouchDownX) >= mTouchSlope
                                    || Math.abs(y - mTouchDownY) >= mTouchSlope) {
                                touchDown(mGestureListener.inverseX(mTouchDownX), mGestureListener.inverseY(mTouchDownY), mTouchDownAngle);
                                mTouchState = 1;
                            }
                        }
                        if (mTouchState == 1) {
                            touchMove(mGestureListener.inverseX(x), mGestureListener.inverseY(y), angle);
                        }
                    } else {
                        if (mTouchState == 1) {
                            touchUp(mGestureListener.inverseX(x), mGestureListener.inverseY(y), angle);
                        }
                        mTouchState = -1;
                        releaseVelocityTracker();
                    }
                }
                invalidate();
            }
        }
        if (mTouchState != -2) {
            mGestureRecognizer.onNormalTouchEvent(event);
        }
        return true;
    }

    @Override
    public void onViewRectChanged() {
        invalidate();
    }

    private boolean isCanvasRange(float x, float y) {
        if (x < 0 || x > mForceWidth) return false;
        if (y < 0 || y > mForceHeight) return false;
        return true;
    }

    public boolean onBackPressed() {
        return mGestureListener.onBackPressed();
    }

    protected void touchDown(float x, float y, float angle) {
        int routeCount = mRoutes.size();
        if (mCurrentRouteId < routeCount - 1) {
            for (int i = routeCount - 1; i > mCurrentRouteId; --i) {
                mRoutes.remove(i);
            }
            mKeyFrameManager.abandonFrom(mCurrentRouteId);
            if (mCurrentRouteId == -1) {
                mRoutes.clear();
                mActions.clear();
                mTempActions.clear();
                mCurrentActionID = 0;
            } else {
                for (int i = mActions.size() - 1; i >= 0; --i) {
                    Action action = mActions.get(i);
                    if (action.mType == Action.ACTION_TYPE_DRAW && action.mValue == mCurrentRouteId) {
                        mCurrentActionID = i;
                        break;
                    }
                }
            }
            updateAction(Action.ACTION_TYPE_CHANGE_BRUSH, mBrushType);
            updateAction(Action.ACTION_TYPE_CHANGE_WIDTH, mBrushWidth);
            updateAction(Action.ACTION_TYPE_CHANGE_COLOR, mBrushColor);
            updateAction(Action.ACTION_TYPE_CHANGE_ALPHA, mBrushAlpha);
        }

        if (mColorPicker != null) {
            int color = mColorPicker.getResultColor();
            mColorPicker.setCurrentColor(color, false);
        }

        mBounds.setEmpty();
        mCurrentRoute = new Route();
        mCurrentRouteId = mRoutes.size();
        mRoutes.add(mCurrentRoute);
        Particle particle = new Particle(x, y, angle);
        mCurrentRoute.add(particle);

        mBrushAlphaPaint.setAlpha(mBrushAlpha);
        mBrush.setBrushLayer(mBrushLayer, mBrushLayerCanvas);

        long startTime = System.currentTimeMillis();
        mBrush.beginRoute(mBrushLayerCanvas, particle, mBounds);
        mCurrentRoute.mDrawTime = (System.currentTimeMillis() - startTime);

        mLastX = x;
        mLastY = y;
        mLastAngle = angle;

        notifyRouteStateChanged();
    }

    protected void touchMove(float x, float y, float angle) {
        float dx = Math.abs(x - mLastX);
        float dy = Math.abs(y - mLastY);
        if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
            Particle particle = new Particle((x + mLastX) / 2, (y + mLastY) / 2, (angle + mLastAngle) / 2);
            mCurrentRoute.add(particle);
            long startTime = System.currentTimeMillis();
            mBrush.drawRoute(mBrushLayerCanvas, particle, mBounds);
            mCurrentRoute.mDrawTime += (System.currentTimeMillis() - startTime);
            mLastX = x;
            mLastY = y;
            mLastAngle = angle;
        }
    }

    protected void touchUp(float x, float y, float angle) {
        Particle particle = new Particle(mLastX, mLastY, mLastAngle);
        mCurrentRoute.add(particle);
        long startTime = System.currentTimeMillis();
        mBrush.endRoute(mBrushLayerCanvas, particle, mBounds);
        commitBrushLayer(mBrush instanceof EraserBrush ? true : false);
        updateDrawing();
        mCurrentRoute.mDrawTime += (System.currentTimeMillis() - startTime);
        updateAction(Action.ACTION_TYPE_DRAW, mRoutes.size() - 1);
        long totalDrawTimes = 0;
        int start = mKeyFrameManager.getLastKeyFrameRouteId() + 1;
        for (int i = start; i <= mCurrentRouteId; ++i) {
            totalDrawTimes += mRoutes.get(i).mDrawTime;
            if (totalDrawTimes >= DRAW_LIMIT_TIME) {
                mKeyFrameManager.addKeyFrame();
                break;
            }
        }
        if (mCurrentRouteId <= mSavedRoutId) {
            mSavedRoutId = -1;
        }
        if (mPaintStateListener != null) {
            mPaintStateListener.onEditUp();
        }
    }

    public void setPaintStateListener(PaintStateListener listener) {
        mPaintStateListener = listener;
    }

    public boolean needSave() {
        return mCurrentRouteId != mSavedRoutId;
    }

    public int getCurrentRouteId() {
        return mCurrentRouteId;
    }

    public void onSave(boolean save) {
        if (save) {
            mSavedRoutId = mCurrentRouteId;
        } else {
            mCurrentRouteId = mSavedRoutId;
        }
    }

    public boolean canUndo() {
        return mRoutes.size() > 0 && mCurrentRouteId >= 0;
    }

    public boolean checkTheme() {
        return mOldTheme != mTheme;
    }

    public int getCurrentTheme() {
        return mTheme;
    }

    public boolean canRedo() {
        return mRoutes.size() > 0 && mCurrentRouteId < mRoutes.size() - 1;
    }

    public void undo() {
        if (!canUndo()) {
            return;
        }
        mCurrentRouteId--;

        playback();
    }

    public void playback() {
        KeyFrame keyFrame = mKeyFrameManager.getKeyFrame(mCurrentRouteId);

        int startActionID = 0;
        if (keyFrame != null && mKeyFrameManager.waitFrameLoadingDone(keyFrame)) {
            startActionID = keyFrame.mActionId + 1;
            applyKeyFrame(keyFrame);
        } else {
            if (mPaintCanvas != null && mBitmapBackup != null) {
                if (mMaskLayerCanvas != null) {
                    mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
                    mMaskLayerCanvas.drawBitmap(mBitmapBackup, 0, 0, null);
                }
            }
        }
        playbackToCurrentRouteId(startActionID);
    }

    public void redo() {
        if (!canRedo()) {
            return;
        }
        mCurrentRouteId++;
        KeyFrame keyFrame = mKeyFrameManager.getKeyFrame(mCurrentRouteId);
        if (keyFrame != null && keyFrame.mRouteId == mCurrentRouteId && mKeyFrameManager.waitFrameLoadingDone(keyFrame)) {
            mCurrentActionID = keyFrame.mActionId + 1;
            applyKeyFrame(keyFrame);
        } else {
            playbackToCurrentRouteId(mCurrentActionID);
        }
    }

    private void applyKeyFrame(KeyFrame keyFrame) {
        mPlaybackBrush = Brush.getBrush(getContext(), Brush.TARGET_PLAYBACK, keyFrame.mBrushType);
        mPlaybackBrush.setBrushLayer(mBrushLayer, mBrushLayerCanvas);
        mPlaybackBrush.setPaper(mPaper);
        mPlaybackBrush.setWidth(keyFrame.mBrushWidth);
        mPlaybackBrush.setColor(keyFrame.mBrushColor);
        mPlaybackBrush.setAlpha(keyFrame.mBrushAlpha);
        mBrushAlphaPaint.setAlpha(keyFrame.mBrushAlpha);

        if (mMaskLayerCanvas != null) {
            mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
            mMaskLayerCanvas.drawBitmap(keyFrame.mBitmap, 0, 0, null);
        }
    }

    private void playbackToCurrentRouteId(int startActionId) {
        int actionCount = mActions.size();
        for (int i = startActionId; i < actionCount; i++) {
            Action action = mActions.get(i);
            if (action.mType == Action.ACTION_TYPE_CHANGE_BRUSH) {
                mPlaybackBrush = Brush.getBrush(getContext(), Brush.TARGET_PLAYBACK, action.mValue);
                mPlaybackBrush.setPaper(mPaper);
            } else if (action.mType == Action.ACTION_TYPE_CHANGE_WIDTH) {
                mPlaybackBrush.setWidth(action.mValue);
            } else if (action.mType == Action.ACTION_TYPE_CHANGE_COLOR) {
                mPlaybackBrush.setColor(action.mValue);
            } else if (action.mType == Action.ACTION_TYPE_CHANGE_ALPHA) {
                mBrushAlphaPaint.setAlpha(action.mValue);
                mPlaybackBrush.setAlpha(action.mValue);
            } else {
                mCurrentActionID = i;
                if (action.mValue > mCurrentRouteId) {
                    break;
                }
                List<Particle> route = mRoutes.get(action.mValue);
                int routeCount = route.size();
                if (routeCount > 0) {
                    mBounds.setEmpty();
                    boolean isEraserBrush = mPlaybackBrush instanceof EraserBrush ? true : false;
                    if (isEraserBrush) {
                        mPlaybackBrush.setPaper(mPaper);
                        mPlaybackBrush.setColor(mEraserColor);
                    }
                    mPlaybackBrush.setBrushLayer(mBrushLayer, mBrushLayerCanvas);
                    mPlaybackBrush.beginRoute(mBrushLayerCanvas, route.get(0), mBounds);
                    for (int j = 1; j < routeCount - 1; j++) {
                        mPlaybackBrush.drawRoute(mBrushLayerCanvas, route.get(j), mBounds);
                    }
                    mPlaybackBrush.endRoute(mBrushLayerCanvas, route.get(routeCount - 1), mBounds);
                    commitBrushLayer(isEraserBrush);
                }
            }
        }
    }

    private void commitBrushLayer(boolean isEraserBrush) {
        if (!mBounds.isEmpty()) {
            if (mMaskLayerCanvas != null) {
                mMaskLayerCanvas.save(Canvas.CLIP_SAVE_FLAG);
                mMaskLayerCanvas.clipRect(mBounds);
                if (mPaintingLayers && isEraserBrush) {
                    Paint pp = new Paint(Paint.ANTI_ALIAS_FLAG);
                    pp.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));
                    pp.setAlpha(mBrushAlphaPaint.getAlpha());
                    mMaskLayerCanvas.drawBitmap(mBrushLayer, 0, 0, pp);
                } else {
                    mMaskLayerCanvas.drawBitmap(mBrushLayer, 0, 0, mBrushAlphaPaint);
                }
                mMaskLayerCanvas.restore();
                mBrush.clearBrushLayer(mBounds);
                mBounds.setEmpty();
            }
        }
    }

    private int mBgColor = Color.WHITE;

    public void changeBgColor(int color) {
        mBgColor = color;
        mBgShader = null;
        mBgPaint.setShader(null);
        mBgPaint.setColor(color);
        updateDrawing();
    }

    public int getBgColor() {
        return mBgColor;
    }

    public BitmapShader getBgShader() {
        return mBgShader;
    }

    private BitmapShader mBgShader = null;
    private Paint mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    private ChangeBgAnim mChangeBgAnim;
    private Paint mChangeAnimPaint;
    private int mChangeBgCount = -1;

    public void changeBgDrawable(int theme, float center) {
        if (!(theme < 0 || theme > Theme.THEME_ALL - 1)) {
            mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
            mBrushLayerCanvas.drawRect(0, 0, mPaintBitmap.getWidth(), mPaintBitmap.getHeight(), mBgPaint);

            mBgShader = new BitmapShader(Theme.THEME_LIST[theme].getPaper(getContext()), TileMode.REPEAT, TileMode.REPEAT);
            mBgPaint.setStyle(Style.FILL);
            mBgPaint.setShader(mBgShader);
            if (center != -1) {
                if (mChangeBgAnim == null) {
                    mChangeBgAnim = new ChangeBgAnim(getResources());
                    mChangeAnimPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
                    mChangeAnimPaint.setStyle(Style.FILL);
                }
                mChangeAnimPaint.setShader(mBgShader);
                mChangeAnimPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
                mChangeBgAnim.prepare(mChangeAnimPaint, center);
                mChangeBgCount = 0;
            }
            updateDrawing();
        }
    }

    private void commitInsertPic() {
        Matrix inverseMatrix = new Matrix(mInsertImageMatrix);
        inverseMatrix.postTranslate(-mGestureListener.getTranslateX() - mForceWidth / 2, -mGestureListener.getTranslateY() - mForceHeight / 2);
        inverseMatrix.postScale(mGestureListener.getInverseScale(), mGestureListener.getInverseScale());
        inverseMatrix.postTranslate(mForceWidth / 2, mForceHeight / 2);
        if (mMaskLayerCanvas != null) {
            mMaskLayerCanvas.drawBitmap(mTextureOrCutBitmap, inverseMatrix, mPaintingPaint);
            updateDrawing();
        }

        releaseInsertPicData();
    }

    private Shader mTextureShader = null;

    private void genTextureBitmap() {
        mEdgePath.computeBounds(mTexturePathRect, false);
        if ((int) mTexturePathRect.width() <= 0 || (int) mTexturePathRect.height() <= 0) {
            return;
        }

        mFillTextureCommit = true;
        mTextureOrCutBitmap = Bitmap.createBitmap((int) mTexturePathRect.width(), (int) mTexturePathRect.height(), Config.ARGB_8888);

        Path tmp = new Path();
        tmp.set(mEdgePath);
        tmp.offset(-mTexturePathRect.left, -mTexturePathRect.top);

        mTextureOrCutCanvas.setBitmap(mTextureOrCutBitmap);
        mFillTexturePaint.setShader(mTextureShader);
        mFillTexturePaint.setAlpha(mBrushAlpha);
        mTextureOrCutCanvas.drawPath(tmp, mFillTexturePaint);
        mTextureOrCutCanvas.drawColor(mColorPicker.getResultColor(), Mode.SRC_IN);

        mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
        mBrushLayerCanvas.drawBitmap(mMaskLayer, 0, 0, null);
    }

    private void commitFillTexture() {
        if (mTextureOrCutBitmap != null) {
            float left = mGestureListener.inverseX(mTexturePathRect.left);
            float top = mGestureListener.inverseY(mTexturePathRect.top);
            float right = mGestureListener.inverseX(mTexturePathRect.right);
            float bottom = mGestureListener.inverseY(mTexturePathRect.bottom);
            mTexturePathRect.set(left, top, right, bottom);

            mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
            mMaskLayerCanvas.drawBitmap(mBrushLayer, 0, 0, null);
            mMaskLayerCanvas.drawBitmap(mTextureOrCutBitmap, null, mTexturePathRect, null);

            mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
            updateDrawing();

            mEdgePath.reset();
            mTextureOrCutBitmap.recycle();
            mTextureOrCutBitmap = null;
            mRectRegion.setEmpty();
            mChoiceRegion.setEmpty();
            if (mPaintStateListener != null) {
                mPaintStateListener.onEditUp();
            }
        }
    }

    private Rect mCutSrcRect = new Rect();
    private Rect mCutDstRect = new Rect();

    private void commitCutBitmap() {
        if (mTextureOrCutBitmap != null) {
            float left = mGestureListener.inverseX(mCutPathRect.left);
            float top = mGestureListener.inverseY(mCutPathRect.top);
            float right = mGestureListener.inverseX(mCutPathRect.right);
            float bottom = mGestureListener.inverseY(mCutPathRect.bottom);
            mCutPathRect.set(left, top, right, bottom);
            mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
            mMaskLayerCanvas.drawBitmap(mBrushLayer, 0, 0, null);
            mMaskLayerCanvas.drawBitmap(mTextureOrCutBitmap, null, mCutPathRect, null);

            mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
            updateDrawing();

            mTextureOrCutBitmap.recycle();
            mTextureOrCutBitmap = null;
            mCutPathRect.setEmpty();
            mRectRegion.setEmpty();
            mChoiceRegion.setEmpty();
        }
        mEdgePath.reset();
        mCutEdgePath.reset();
        mInGenCutBitmap = false;
    }

    private void genCutBitmap() {
        mCutOriginRect.set(mCutPathRect);
        if (mCutPathRect.width() == 0 || mCutPathRect.height() == 0) {
            return;
        }

        float cutPathLeft = mGestureListener.inverseX(mCutPathRect.left);
        float cutPathTop = mGestureListener.inverseY(mCutPathRect.top);
        float cutPathRight = mGestureListener.inverseX(mCutPathRect.right);
        float cutPathBottom = mGestureListener.inverseY(mCutPathRect.bottom);

        mTextureOrCutBitmap = Bitmap.createBitmap((int) mCutPathRect.width(), (int) mCutPathRect.height(), Config.ARGB_8888);

        mCutSrcRect.set((int) cutPathLeft, (int) cutPathTop, (int) cutPathRight, (int) cutPathBottom);
        mCutDstRect.set(0, 0, (int) mCutPathRect.width(), (int) mCutPathRect.height());

        Path tmp = new Path();
        tmp.set(mCutEdgePath);
        tmp.offset(-mCutPathRect.left, -mCutPathRect.top);

        // gen cut fg bitmap
        mTextureOrCutCanvas.setBitmap(mTextureOrCutBitmap);
        mCutPaint.setXfermode(null);
        mTextureOrCutCanvas.drawPath(tmp, mCutPaint);
        mCutPaint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));

        mTextureOrCutCanvas.drawBitmap(mMaskLayer, mCutSrcRect, mCutDstRect, mCutPaint);

        // gen cut bg bitmap
        mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
        mBrushLayerCanvas.drawBitmap(mMaskLayer, 0, 0, null);
        mBrushLayerCanvas.save(Canvas.CLIP_SAVE_FLAG);
        mBrushLayerCanvas.clipPath(mInverseEdgePath);
        if (!mPaintingLayers) {
            if (mPaper != null) {
                mBrushLayerCanvas.drawRect(0, 0, Constant.PAINTING_WIDTH, Constant.PAINTING_HEIGHT, mBgPaint);
            } else {
                mBrushLayerCanvas.drawColor(mTheme);
            }
        } else {
            mBrushLayerCanvas.drawColor(0x0, Mode.CLEAR);
        }
        mBrushLayerCanvas.restore();

        mMaskLayerCanvas.drawColor(0x0, Mode.CLEAR);
        mMaskLayerCanvas.drawBitmap(mBrushLayer, 0, 0, null);

        updateDrawing();
    }

    private void commitBitmap(int type) {
        int routeCount = mRoutes.size();
        if (mCurrentRouteId < routeCount - 1) {
            for (int i = routeCount - 1; i > mCurrentRouteId; --i) {
                mRoutes.remove(i);
            }
            mKeyFrameManager.abandonFrom(mCurrentRouteId);
            if (mCurrentRouteId == -1) {
                mRoutes.clear();
                mActions.clear();
                mTempActions.clear();
                mCurrentActionID = 0;
            } else {
                for (int i = mActions.size() - 1; i >= 0; --i) {
                    Action action = mActions.get(i);
                    if (action.mType == Action.ACTION_TYPE_DRAW && action.mValue == mCurrentRouteId) {
                        mCurrentActionID = i;
                        break;
                    }
                }
            }
            updateAction(Action.ACTION_TYPE_CHANGE_BRUSH, mBrushType);
            updateAction(Action.ACTION_TYPE_CHANGE_WIDTH, mBrushWidth);
            updateAction(Action.ACTION_TYPE_CHANGE_COLOR, mBrushColor);
            updateAction(Action.ACTION_TYPE_CHANGE_ALPHA, mBrushAlpha);
        }

        mCurrentRoute = new Route();
        mCurrentRoute.mDrawTime = DRAW_LIMIT_TIME;
        mCurrentRouteId = mRoutes.size();
        Utils.log("lq-commitInsertBitmap: %d", mCurrentRouteId);
        mRoutes.add(mCurrentRoute);

        if (type == COMMIT_INSERT_PICTURE) {
            commitInsertPic();
        } else if (type == COMMIT_FILL_TEXTURE) {
            commitFillTexture();
        } else if (type == COMMIT_CUT_BITMAP) {
            commitCutBitmap();
        }

        updateAction(Action.ACTION_TYPE_DRAW, mRoutes.size() - 1);

        long totalDrawTimes = 0;
        int start = mKeyFrameManager.getLastKeyFrameRouteId() + 1;
        for (int i = start; i <= mCurrentRouteId; ++i) {
            totalDrawTimes += mRoutes.get(i).mDrawTime;
            if (totalDrawTimes >= DRAW_LIMIT_TIME) {
                Utils.log("lq-commitInsertBitmap: %d, will add to keyframe", totalDrawTimes);
                mKeyFrameManager.addKeyFrame();
                break;
            }
        }
        Utils.log("lq-commitInsertBitmap: %d, %d", mCurrentRouteId, mSavedRoutId);
        if (mCurrentRouteId <= mSavedRoutId) {
            mSavedRoutId = -1;
        }

        notifyRouteStateChanged();
    }

    public static final int PAINT_MODE_EDIT = 1;
    public static final int PAINT_MODE_NEW = 2;
    private int mPaintMode = PAINT_MODE_EDIT;

    public void setPaintMode(int mode) {
        mPaintMode = mode;
    }

    public int getPaintMode() {
        return mPaintMode;
    }

    private void notifyRouteStateChanged() {
        if (mPaintStateListener != null) {
            mPaintStateListener.onRouteStateChanged();
        }
    }

    protected void acquireVelocityTracker(final MotionEvent event) {
        if (null == mVelocityTracker) {
            mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
    }

    protected void releaseVelocityTracker() {
        if (null != mVelocityTracker) {
            mVelocityTracker.clear();
            mVelocityTracker.recycle();
            mVelocityTracker = null;
        }
    }

    private static Bitmap sBackupBitmap = null;

    private static Bitmap getBackupBitmap(int width, int height) {
        if (sBackupBitmap == null || sBackupBitmap.getWidth() != width || sBackupBitmap.getHeight() != height) {
            sBackupBitmap = BitmapUtils.createBitmap(width, height, MeasuredAsyncDrawable.DRAWABLE_TYPE_IMAGE, Config.ARGB_8888);
        }
        return sBackupBitmap;
    }

    private static Bitmap sBrushLayerBitmap = null;

    private static Bitmap getBrushLayerBitmap(int width, int height) {
        if (sBrushLayerBitmap == null || sBrushLayerBitmap.getWidth() != width || sBrushLayerBitmap.getHeight() != height) {
            sBrushLayerBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        }
        return sBrushLayerBitmap;
    }

    private static Bitmap sMaskLayerBitmap = null;

    private static Bitmap getMaskLayerBitmap(int width, int height) {
        if (sMaskLayerBitmap == null || sMaskLayerBitmap.getWidth() != width || sMaskLayerBitmap.getHeight() != height) {
            sMaskLayerBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        }
        return sMaskLayerBitmap;
    }

    private static Bitmap sPaintBitmap = null;

    private static Bitmap getPaintBitmap(int width, int height) {
        if (sPaintBitmap == null || sPaintBitmap.getWidth() != width || sPaintBitmap.getHeight() != height) {
            sPaintBitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
        }
        return sPaintBitmap;
    }

    private Bitmap copyBitmap(Bitmap src) {
        Bitmap dst = Bitmap.createBitmap(src.getWidth(), src.getHeight(), Config.ARGB_8888);
        if (dst != null) {
            Canvas c = new Canvas(dst);
            c.drawColor(0x0, Mode.CLEAR);// liuqiang add
            c.drawBitmap(src, 0, 0, null);
        }
        return dst;
    }

    private class KeyFrameManager {

        public List<KeyFrame> mKeyFrames;

        public KeyFrameManager() {
            mKeyFrames = new ArrayList<KeyFrame>();
        }

        public void addKeyFrame() {
            KeyFrame frame = new KeyFrame();
            frame.mKeyFrameId = mKeyFrames.size();
            frame.mActionId = mActions.size() - 1;
            frame.mRouteId = mRoutes.size() - 1;
            frame.mBrushType = mBrushType;
            frame.mBrushWidth = mBrushWidth;
            frame.mBrushColor = mBrushColor;
            frame.mBrushAlpha = mBrushAlpha;
            if (mMaskLayerCanvas != null) {
                frame.mBitmap = copyBitmap(mMaskLayer);
            }
            frame.mSaved = false;
            mKeyFrames.add(frame);
            release(frame.mKeyFrameId - 2);
        }

        public KeyFrame getKeyFrame(int routeId) {
            for (int i = mKeyFrames.size() - 1; i >= 0; i--) {
                KeyFrame frame = mKeyFrames.get(i);
                if (frame.mRouteId <= routeId) {
                    int id = frame.mKeyFrameId;
                    release(id - 2);
                    release(id + 2);
                    load(id);
                    load(id - 1);
                    load(id + 1);
                    return frame;
                }
            }
            return null;
        }

        public void abandonFrom(int routeId) {
            int i = mKeyFrames.size() - 1;
            for (; i >= 0; i--) {
                KeyFrame frame = mKeyFrames.get(i);
                if (frame.mRouteId <= routeId) {
                    break;
                }
            }
            for (int j = mKeyFrames.size() - 1; j > i; --j) {
                KeyFrame frame = mKeyFrames.get(j);
                if (frame.mBitmap != null) {
                    Utils.log("abandonFrom:111 %d, %d, %d, %d, %d", i, routeId, frame.mKeyFrameId, frame.mActionId, frame.mRouteId);
                    BitmapPool.getPool(MeasuredAsyncDrawable.DRAWABLE_TYPE_IMAGE).recycle(frame.mBitmap);
                    frame.mBitmap = null;
                }
                Utils.log("abandonFrom:222 %d, %d, %d, %d, %d", i, routeId, frame.mKeyFrameId, frame.mActionId, frame.mRouteId);
                mKeyFrames.remove(j);
            }
        }

        private int getLastKeyFrameRouteId() {
            int size = mKeyFrames.size();
            if (size > 0) {
                return mKeyFrames.get(size - 1).mRouteId;
            }
            return -1;
        }

        private void load(int id) {
            if (id >= 0 && id < mKeyFrames.size()) {
                final KeyFrame frame = mKeyFrames.get(id);
                if (frame.mSaved && frame.mBitmap == null) {
                    synchronized (frame) {
                        if (!frame.mLoading) {
                            frame.mLoading = true;
                            ThreadPool.getInstance().submit(new Job<Void>() {
                                @Override
                                public Void run(JobContext jc) {
                                    String path = PainterUtils.getTempFilePath(getContext(), frame.mKeyFrameId);
                                    frame.mBitmap = BitmapUtils.loadBitmapFromFile(path, Constant.PAINTING_WIDTH, Constant.PAINTING_HEIGHT,
                                            BitmapUtils.FIT_LENGTH_LARGGER, MeasuredAsyncDrawable.DRAWABLE_TYPE_IMAGE, 0);
                                    //frame.mBitmap = PainterUtils.loadBitmapFromFile(path, mBitmap.getWidth(), mBitmap.getHeight());
                                    if (frame.mBitmap == null) {
                                        frame.mBitmap = mMaskLayer;
                                    }
                                    synchronized (frame) {
                                        frame.mLoading = false;
                                        frame.notifyAll();
                                    }
                                    return null;
                                }
                            });
                        }
                    }
                }
            }
        }

        private boolean waitFrameLoadingDone(KeyFrame keyFrame) {
            if (keyFrame.mLoading) {
                synchronized (keyFrame) {
                    try {
                        Utils.log("waitFrameLoadingDone:111: %d", keyFrame.mRouteId);
                        keyFrame.wait();
                        Utils.log("waitFrameLoadingDone:222: %d", keyFrame.mRouteId);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            return keyFrame.mBitmap != null;
        }

        private void release(int id) {
            if (id >= 0 && id < mKeyFrames.size()) {
                final KeyFrame frame = mKeyFrames.get(id);
                if (!frame.mSaved && frame.mBitmap != null) {
                    synchronized (frame) {
                        if (!frame.mSaving) {
                            frame.mSaving = true;
                            ThreadPool.getInstance().submit(new Job<Void>() {
                                @Override
                                public Void run(JobContext jc) {
                                    Utils.log("release:111 %d, %d, %d", frame.mKeyFrameId, frame.mActionId, frame.mRouteId);
                                    FileOutputStream fOut = null;
                                    String filePath = PainterUtils.getTempFilePath(getContext(), frame.mKeyFrameId);
                                    try {
                                        File f = new File(filePath);
                                        if (f.exists()) {
                                            f.delete();
                                        }
                                        f.createNewFile();
                                        fOut = new FileOutputStream(f);
                                        frame.mBitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
                                        fOut.flush();
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    } finally {
                                        Utils.closeSilently(fOut);
                                        BitmapPool.getPool(MeasuredAsyncDrawable.DRAWABLE_TYPE_IMAGE).recycle(frame.mBitmap);
                                        frame.mBitmap = null;
                                        frame.mSaved = true;
                                    }
                                    synchronized (frame) {
                                        frame.mSaving = false;
                                    }
                                    return null;
                                }
                            });
                        }
                    }
                } else {
                    if (frame.mBitmap != null) {
                        Utils.log("release:222 %d, %d, %d", frame.mKeyFrameId, frame.mActionId, frame.mRouteId);
                        BitmapPool.getPool(MeasuredAsyncDrawable.DRAWABLE_TYPE_IMAGE).recycle(frame.mBitmap);
                        frame.mBitmap = null;
                    }
                }
            }
        }
    }

    public static class SavedState extends BaseSavedState {

        private List<Route> routes;
        private List<Action> actions;
        private List<Action> tempActions;
        private List<KeyFrame> keyFrames;
        private int currentRouteId = -1;
        private int currentActionID = 0;

        SavedState(Parcelable superState) {
            super(superState);
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            super.writeToParcel(out, flags);

            out.writeInt(routes.size());
            for (Route r : routes) {
                out.writeInt(r.size());
                for (Particle p : r) {
                    out.writeFloat(p.x);
                    out.writeFloat(p.y);
                    out.writeFloat(p.angle);
                }
            }

            out.writeInt(actions.size());
            for (Action a : actions) {
                out.writeInt(a.mType);
                out.writeInt(a.mValue);
            }

            out.writeInt(tempActions.size());
            for (Action a : tempActions) {
                out.writeInt(a.mType);
                out.writeInt(a.mValue);
            }

            out.writeInt(keyFrames.size());
            for (KeyFrame k : keyFrames) {
                out.writeInt(k.mKeyFrameId);
                out.writeInt(k.mActionId);
                out.writeInt(k.mRouteId);
                out.writeInt(k.mBrushType);
                out.writeInt(k.mBrushWidth);
                out.writeInt(k.mBrushColor);
                out.writeInt(k.mBrushAlpha);
                out.writeInt(k.mSaved ? 1 : 0);
            }

            out.writeInt(currentRouteId);
            out.writeInt(currentActionID);
        }

        public static final Creator<SavedState> CREATOR
                = new Creator<SavedState>() {

            public SavedState createFromParcel(Parcel in) {
                return new SavedState(in);
            }

            public SavedState[] newArray(int size) {
                return new SavedState[size];
            }
        };

        private SavedState(Parcel in) {
            super(in);

            int length = in.readInt();
            routes = new ArrayList<Route>(length);
            for (int i = 0; i < length; ++i) {
                int l = in.readInt();
                Route r = new Route(l);
                for (int j = 0; j < l; ++j) {
                    Particle p = new Particle();
                    p.x = in.readFloat();
                    p.y = in.readFloat();
                    p.angle = in.readFloat();
                    r.add(p);
                }
                routes.add(r);
            }

            length = in.readInt();
            actions = new ArrayList<Action>(length);
            for (int i = 0; i < length; ++i) {
                Action a = new Action();
                a.mType = in.readInt();
                a.mValue = in.readInt();
                actions.add(a);
            }

            length = in.readInt();
            tempActions = new ArrayList<Action>(length);
            for (int i = 0; i < length; ++i) {
                Action a = new Action();
                a.mType = in.readInt();
                a.mValue = in.readInt();
                tempActions.add(a);
            }

            length = in.readInt();
            keyFrames = new ArrayList<KeyFrame>(length);
            for (int i = 0; i < length; ++i) {
                KeyFrame k = new KeyFrame();
                k.mKeyFrameId = in.readInt();
                k.mActionId = in.readInt();
                k.mRouteId = in.readInt();
                k.mBrushType = in.readInt();
                k.mBrushWidth = in.readInt();
                k.mBrushColor = in.readInt();
                k.mBrushAlpha = in.readInt();
                k.mSaved = in.readInt() != 0;
                keyFrames.add(k);
            }

            currentRouteId = in.readInt();
            currentActionID = in.readInt();
        }
    }

    // liuqiang start
    private boolean mIsDeleteOp = false;
    private boolean mIsScaleOp = false;
    private boolean mIsMoveOp = false;
    private float mDownX, mDownY;
    private float mTouchedLastX, mTouchedLastY;
    private float mCutPathLastX, mCutPathLastY;
    private float mInverseTouchedLastX, mInverseTouchedLastY;

    private void setInsertImagePosition(float x, float y, int action) {
        if (action == MotionEvent.ACTION_DOWN) {
            mDownX = x;
            mDownY = y;
            mTouchedLastX = mDownX;
            mTouchedLastY = mDownY;
            mRectRegion.set((int) mInsertRect.left, (int) mInsertRect.top, (int) mInsertRect.right, (int) mInsertRect.bottom);
            mChoiceRegion.setPath(mEdgePath, mRectRegion);
            if (mScaleInsertRect.contains((int) mDownX, (int) mDownY)) {
                mIsScaleOp = true;
            } else if (mDeleteInsertRect.contains((int) mDownX, (int) mDownY)) {
                mIsDeleteOp = true;
            } else if (mChoiceRegion.contains((int) mDownX, (int) mDownY)) {
                mIsMoveOp = true;
            }
            mTouchState = 0;
        } else if (action == MotionEvent.ACTION_MOVE) {
            // this is a move event
            if (Math.abs(x - mDownX) >= mTouchSlope || Math.abs(y - mDownY) >= mTouchSlope) {
                updateInsertView((int) x, (int) y);
            }
        } else if (action == MotionEvent.ACTION_UP) {
            if (mIsDeleteOp) {
                // delete the inserted bitmap
                releaseInsertPicData();
                mPaintStateListener.onInsertFinish();
            } else if (mIsScaleOp) {
                mIsScaleOp = false;
            }
            mIsMoveOp = false;
        }
    }

    private void releaseInsertPicData() {
        mInInsertImage = false;

        mTextureOrCutBitmap.recycle();
        mTextureOrCutBitmap = null;
        mInsertScaleBitmap.recycle();
        mInsertScaleBitmap = null;
        mInsertDeleteBitmap.recycle();
        mInsertDeleteBitmap = null;

        mRectRegion.setEmpty();
        mChoiceRegion.setEmpty();

        mEdgeDownPathPaint.reset();
        mInsertImageMatrix.reset();
        mEdgePath.reset();

        mInsertRect.setEmpty();
        mDeleteInsertRect.setEmpty();
        mScaleInsertRect.setEmpty();
    }

    private void updateInsertView(int x, int y) {
        if (mIsMoveOp) {
            int deltaX = (int) (x - mTouchedLastX);
            int deltaY = (int) (y - mTouchedLastY);
            mInsertCenterX += deltaX;
            mInsertCenterY += deltaY;

            mInsertImageMatrix.postTranslate(deltaX, deltaY);

            mTouchedLastX = x;
            mTouchedLastY = y;

            //update 4 vertexs
            mInsertImageMatrix.mapPoints(mDstPoints, mSrcPoints);

            // reset the insert data
            mDeleteInsertRect.set((int) mDstPoints[LT_X] - mHalfButSize, (int) mDstPoints[LT_Y] - mHalfButSize, (int) mDstPoints[LT_X] + mHalfButSize, (int) mDstPoints[LT_Y] + mHalfButSize);
            mScaleInsertRect.set((int) mDstPoints[RB_X] - mHalfButSize, (int) mDstPoints[RB_Y] - mHalfButSize, (int) mDstPoints[RB_X] + mHalfButSize, (int) mDstPoints[RB_Y] + mHalfButSize);

            mEdgePath.reset();
            mEdgePath.moveTo(mDstPoints[RB_X], mDstPoints[RB_Y]);
            mEdgePath.lineTo(mDstPoints[LB_X], mDstPoints[LB_Y]);
            mEdgePath.lineTo(mDstPoints[LT_X], mDstPoints[LT_Y]);
            mEdgePath.lineTo(mDstPoints[RT_X], mDstPoints[RT_Y]);
            mEdgePath.lineTo(mDstPoints[RB_X], mDstPoints[RB_Y]);
            mEdgePath.computeBounds(mInsertRect, false);
        } else if (mIsScaleOp) {
            // first cal scale
            float initDistance = PainterUtils.getDistance(mInsertCenterX, mInsertCenterY, mTouchedLastX, mTouchedLastY);
            float finalDistance = PainterUtils.getDistance(mInsertCenterX, mInsertCenterY, x, y);
            float s = finalDistance / initDistance;
            float scale = s < 1 ? s : mInsertRect.width() > Constant.PAINTING_WIDTH ? 1 : s;
            mInsertImageMatrix.postScale(scale, scale, mInsertCenterX, mInsertCenterY);

            mTouchedLastX = x;
            mTouchedLastY = y;

            float nowAngle = (float) Math.atan2(y * 1.0f - mInsertCenterY, x * 1.0f - mInsertCenterX);
            float angleDelta = nowAngle - mInsertLastAngle;
            mInsertLastAngle = nowAngle;

            mInsertImageMatrix.postRotate(angleDelta * 180.0f / Constant.PI, mInsertCenterX, mInsertCenterY);

            mInsertImageMatrix.mapPoints(mDstPoints, mSrcPoints);

            mDeleteInsertRect.set((int) mDstPoints[LT_X] - mHalfButSize, (int) mDstPoints[LT_Y] - mHalfButSize, (int) mDstPoints[LT_X] + mHalfButSize, (int) mDstPoints[LT_Y] + mHalfButSize);
            mScaleInsertRect.set((int) mDstPoints[RB_X] - mHalfButSize, (int) mDstPoints[RB_Y] - mHalfButSize, (int) mDstPoints[RB_X] + mHalfButSize, (int) mDstPoints[RB_Y] + mHalfButSize);

            mEdgePath.reset();
            mEdgePath.moveTo(mDstPoints[RB_X], mDstPoints[RB_Y]);
            mEdgePath.lineTo(mDstPoints[LB_X], mDstPoints[LB_Y]);
            mEdgePath.lineTo(mDstPoints[LT_X], mDstPoints[LT_Y]);
            mEdgePath.lineTo(mDstPoints[RT_X], mDstPoints[RT_Y]);
            mEdgePath.lineTo(mDstPoints[RB_X], mDstPoints[RB_Y]);
            mEdgePath.computeBounds(mInsertRect, false);
        } else if (mIsDeleteOp) {
            mIsDeleteOp = false;
        } else {
            if (mTouchState == 0) {
                mTouchState = -1;
                commitInsert();
            }
        }

        invalidate();
    }

    public static boolean equalFloat(float a, float b) {
        if (Math.abs(b - a) <= 1.0E-2) {
            return true;
        }
        return false;
    }

    private boolean isValidFillPoint = false;
    private float cutX;
    private float cutY;

    private void calFillRegion(float x, float y, int action) {
        float inverseX = mGestureListener.inverseX(x);
        float inverseY = mGestureListener.inverseY(y);
        RectF dstRect = mGestureListener.getDstRect();
        cutX = x;
        cutY = y;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                isValidFillPoint = false;

                mDownX = x;
                mDownY = y;
                mTouchedLastX = mDownX;
                mTouchedLastY = mDownY;
                mInverseTouchedLastX = inverseX;
                mInverseTouchedLastY = inverseY;

                mEdgePath.reset();
                mEdgePath.moveTo(x, y);

                if (inverseX < 0) {
                    cutX = dstRect.left;
                }
                if (inverseX > Constant.PAINTING_WIDTH) {
                    cutX = dstRect.right;
                }
                if (inverseY < 0) {
                    cutY = dstRect.top;
                }
                if (inverseY > Constant.PAINTING_HEIGHT) {
                    cutY = dstRect.bottom;
                }
                mCutPathLastX = cutX;
                mCutPathLastY = cutY;
                mCutEdgePath.reset();
                mCutEdgePath.moveTo(cutX, cutY);
                mInverseEdgePath.reset();
                mInverseEdgePath.moveTo(mInverseTouchedLastX, mInverseTouchedLastY);
                if (mColorPicker != null) {
                    int color = mColorPicker.getResultColor();
                    mColorPicker.setCurrentColor(color, false);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                // this is a move event
                if (Math.abs(x - mDownX) >= mTouchSlope
                        || Math.abs(y - mDownY) >= mTouchSlope) {
                    float dx = Math.abs(x - mDownX);
                    float dy = Math.abs(y - mDownY);
                    if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) {
                        if (!isValidFillPoint) {
                            isValidFillPoint = true;
                        }
                        mEdgePath.quadTo(mTouchedLastX, mTouchedLastY, (x + mTouchedLastX) / 2, (y + mTouchedLastY) / 2);
                        mInverseEdgePath.quadTo(mInverseTouchedLastX, mInverseTouchedLastY, (inverseX + mInverseTouchedLastX) / 2, (inverseY + mInverseTouchedLastY) / 2);
                        if (inverseX < 0) {
                            cutX = dstRect.left;
                        }
                        if (inverseX > Constant.PAINTING_WIDTH) {
                            cutX = dstRect.right;
                        }
                        if (inverseY < 0) {
                            cutY = dstRect.top;
                        }
                        if (inverseY > Constant.PAINTING_HEIGHT) {
                            cutY = dstRect.bottom;
                        }
                        mCutEdgePath.quadTo(mCutPathLastX, mCutPathLastY, (cutX + mCutPathLastX) / 2, (cutY + mCutPathLastY) / 2);
                        mTouchedLastX = x;
                        mTouchedLastY = y;
                        mInverseTouchedLastX = inverseX;
                        mInverseTouchedLastY = inverseY;
                        mCutPathLastX = cutX;
                        mCutPathLastY = cutY;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (isValidFillPoint) {
                    mEdgePath.close();
                    mInverseEdgePath.close();
                    mCutEdgePath.close();
                    // fill texture in the path region
                    if (mInFillTexture) {
                        genTextureBitmap();
                        notifyRouteStateChanged();
                    } else if (mInCut) {
                        mInCutCommit = true;
                        notifyRouteStateChanged();
                    }
                }
                break;
        }
    }

    private void moveTextureBitmap(float x, float y, int action) {
        if (action == MotionEvent.ACTION_DOWN) {
            mRectRegion.set((int) mTexturePathRect.left, (int) mTexturePathRect.top, (int) mTexturePathRect.right, (int) mTexturePathRect.bottom);
            mChoiceRegion.setPath(mEdgePath, mRectRegion);
            mCutLastX = x;
            mCutLastY = y;
            mInTextureBitmapMove = mChoiceRegion.contains((int) x, (int) y);
            mTouchState = 0;
        } else if (action == MotionEvent.ACTION_MOVE) {
            if (mInTextureBitmapMove) {
                mCutCurrentX = x - mCutLastX;
                mCutCurrentY = y - mCutLastY;
                mTexturePathRect.offset(mCutCurrentX, mCutCurrentY);
                mEdgePath.offset(mCutCurrentX, mCutCurrentY);
                mCutLastX = x;
                mCutLastY = y;
            } else {
                if (mTouchState == 0) {
                    mTouchState = 1;
                    mFillTextureCommit = false;
                    commitBitmap(COMMIT_FILL_TEXTURE);
                }
            }
        }
    }

    private boolean mInGenCutBitmap = false;

    private void calCutRegion(float x, float y, int action) {
        if (action == MotionEvent.ACTION_DOWN) {
            mCutEdgePath.computeBounds(mCutPathRect, false);
            mRectRegion.set((int) mCutPathRect.left, (int) mCutPathRect.top, (int) mCutPathRect.right, (int) mCutPathRect.bottom);
            mChoiceRegion.setPath(mCutEdgePath, mRectRegion);
            mCutLastX = x;
            mCutLastY = y;
            mInCutBitmapMove = mChoiceRegion.contains((int) x, (int) y);
            mTouchState = 0;
        } else if (action == MotionEvent.ACTION_MOVE) {
            if (mInCutBitmapMove) {
                if (mInCut && !mInGenCutBitmap) {
                    mInGenCutBitmap = true;
                    genCutBitmap();
                }
                mCutCurrentX = x - mCutLastX;
                mCutCurrentY = y - mCutLastY;
                mCutPathRect.offset(mCutCurrentX, mCutCurrentY);
                mEdgePath.reset();
                mCutEdgePath.offset(mCutCurrentX, mCutCurrentY);
                mCutLastX = x;
                mCutLastY = y;
            } else {
                if (mTouchState == 0) {
                    mTouchState = 1;
                    mInCutCommit = false;
                    commitBitmap(COMMIT_CUT_BITMAP);
                }
            }
        }
    }

    private void setEyeDropperPosition(float x, float y, boolean animate) {
        if (mPaintBitmap == null) {
            return;
        }
        mEyeDropperViewX = x;
        mEyeDropperViewY = y;
        mEyeDropperCurrentX = (int) mGestureListener.inverseX(x);
        mEyeDropperCurrentY = (int) mGestureListener.inverseY(y);
        if (mEyeDropperCurrentX < 0) {
            mEyeDropperCurrentX = 0;
        }
        if (mEyeDropperCurrentX > mPaintBitmap.getWidth() - 1) {
            mEyeDropperCurrentX = mPaintBitmap.getWidth() - 1;
        }
        if (mEyeDropperCurrentY < 0) {
            mEyeDropperCurrentY = 0;
        }
        if (mEyeDropperCurrentY > mPaintBitmap.getHeight() - 1) {
            mEyeDropperCurrentY = mPaintBitmap.getHeight() - 1;
        }
        int eyeDropperViewHalfWdith = mMagnifier.getWidth() / 2;
        int eyeDropperViewMinMargin = eyeDropperViewHalfWdith + Constant.EYEDROPPER_DST_OFFSET;
        if (mEyeDropperViewY < eyeDropperViewMinMargin) {
            if (mEyeDropperViewX < eyeDropperViewMinMargin) {
                if (animate) {
                    if (mEndRotate != 0) {
                        mAnimator.cancel();
                        initEyeDropperRotate(mRotate, 0);
                        mAnimator.start();
                    }
                } else {
                    mEndRotate = 0;
                    mRotate = 0;
                }
            } else {
                if (animate) {
                    if (mEndRotate != 180) {
                        mAnimator.cancel();
                        initEyeDropperRotate(mRotate, 180);
                        mAnimator.start();
                    }
                } else {
                    mEndRotate = 180;
                    mRotate = 180;
                }
            }
        } else {
            if (animate) {
                if (mEndRotate != 90) {
                    mAnimator.cancel();
                    initEyeDropperRotate(mRotate, 90);
                    mAnimator.start();
                }
            } else {
                mEndRotate = 90;
                mRotate = 90;
            }
        }
        mEyeDropperColor = mPaintBitmap.getPixel(mEyeDropperCurrentX, mEyeDropperCurrentY);
        mEyeDropColorOK = !mEyeDropCancelRect.contains((int) x, (int) y);
        if (mEyeDropperChangedListener != null) {
            mEyeDropperChangedListener.onEyeDropperColorChanged(mEyeDropperColor, mEyeDropColorOK);
        }
    }

    private void initEyeDropperRotate(float startRotate, float endRotate) {
        mStartRotate = startRotate;
        mEndRotate = endRotate;
    }

    public void setEyeDropperAngle(float angle) {
        mAngle = angle;
        mRotate = mStartRotate * (1 - mAngle) + mEndRotate * mAngle;
        invalidate();
    }

    public float getEyeDropperAngle() {
        return mAngle;
    }

    private int mInsertCenterX;
    private int mInsertCenterY;

    private boolean mInInsertImage = false;

    private Bitmap mInsertDeleteBitmap = null;
    private Bitmap mInsertScaleBitmap = null;
    private int mHalfButSize = 32;
    private RectF mInsertRect = new RectF();
    private Rect mDeleteInsertRect = new Rect();
    private Rect mScaleInsertRect = new Rect();
    private Matrix mInsertImageMatrix = new Matrix();

    private float mInsertLastAngle;

    private Paint mEdgeDashPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint mEdgeDownPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Paint mEdgeUpPathPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private Path mEdgePath = new Path();
    private Path mCutEdgePath = new Path();
    private Path mInverseEdgePath = new Path();

    private final byte RB_X = 0;
    private final byte RB_Y = RB_X + 1;
    private final byte RT_X = RB_X + 2;
    private final byte RT_Y = RB_X + 3;
    private final byte LT_X = RB_X + 4;
    private final byte LT_Y = RB_X + 5;
    private final byte LB_X = RB_X + 6;
    private final byte LB_Y = RB_X + 7;
    private final float[] mSrcPoints = new float[8];
    private final float[] mDstPoints = new float[8];

    public boolean inInsertImage() {
        return mInInsertImage;
    }

    public void initAndShowInsertImage(Bitmap insertBitmap) {
        if (insertBitmap == null || mInInsertImage) {
            return;
        }
        mTextureOrCutBitmap = insertBitmap;
        if (mTextureOrCutBitmap == null) {
            return;
        }
        if (mRectRegion == null) {
            mRectRegion = new Region();
        }
        if (mChoiceRegion == null) {
            mChoiceRegion = new Region();
        }
        mInsertScaleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.paint_insert_scale);
        mInsertDeleteBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.paint_insert_delete);
        mHalfButSize = mInsertDeleteBitmap.getWidth() / 2;

        mInInsertImage = true;

        mEdgeUpPathPaint.reset();
        mEdgeUpPathPaint.setColor(Color.WHITE);
        mEdgeUpPathPaint.setStyle(Style.STROKE);
        mEdgeUpPathPaint.setStrokeWidth(3);
        mEdgeUpPathPaint.setAntiAlias(true);

        mEdgeDownPathPaint.reset();
        mEdgeDownPathPaint.setColor(getResources().getColor(R.color.eyecircle_paint_size_color));
        mEdgeDownPathPaint.setStyle(Style.STROKE);
        mEdgeDownPathPaint.setStrokeWidth(8);
        mEdgeDownPathPaint.setAntiAlias(true);

        int bitmapWidth = mTextureOrCutBitmap.getWidth();
        int bitmapHeight = mTextureOrCutBitmap.getHeight();

        mInsertCenterX = Constant.PAINTING_WIDTH / 2;
        mInsertCenterY = Constant.PAINTING_HEIGHT / 2;

        mSrcPoints[RB_X] = bitmapWidth;
        mSrcPoints[RB_Y] = bitmapHeight;
        mSrcPoints[RT_X] = bitmapWidth;
        mSrcPoints[RT_Y] = 0;
        mSrcPoints[LT_X] = 0;
        mSrcPoints[LT_Y] = 0;
        mSrcPoints[LB_X] = 0;
        mSrcPoints[LB_Y] = bitmapHeight;

        mInsertImageMatrix.reset();
        mInsertImageMatrix.postTranslate(mInsertCenterX - bitmapWidth / 2, mInsertCenterY - bitmapHeight / 2);

        mInsertImageMatrix.mapPoints(mDstPoints, mSrcPoints);

        mInsertRect.set(mDstPoints[LT_X], mDstPoints[LT_Y], mDstPoints[RB_X], mDstPoints[RB_Y]);
        mDeleteInsertRect.set((int) mDstPoints[LT_X] - mHalfButSize, (int) mDstPoints[LT_Y] - mHalfButSize, (int) mDstPoints[LT_X] + mHalfButSize, (int) mDstPoints[LT_Y] + mHalfButSize);
        mScaleInsertRect.set((int) mDstPoints[RB_X] - mHalfButSize, (int) mDstPoints[RB_Y] - mHalfButSize, (int) mDstPoints[RB_X] + mHalfButSize, (int) mDstPoints[RB_Y] + mHalfButSize);

        mInsertLastAngle = (float) Math.atan2(bitmapHeight, bitmapWidth);

        mEdgePath.reset();
        mEdgePath.addRect(mInsertRect, Direction.CW);

        invalidate();
    }

    private boolean mInFillTexture = false;
    private boolean mFillTextureCommit = false;
    private boolean mInTextureBitmapMove = false;
    private Paint mFillTexturePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

    public void initFillTexture(BitmapShader texture) {
        if (mInFillTexture || texture == null) {
            return;
        }
        mInFillTexture = true;

        if (mRectRegion == null) {
            mRectRegion = new Region();
        }
        if (mChoiceRegion == null) {
            mChoiceRegion = new Region();
        }
        if (mTextureOrCutCanvas == null) {
            mTextureOrCutCanvas = new Canvas();
        }
        if (mTexturePathRect == null) {
            mTexturePathRect = new RectF();
        }

        mEdgePath.reset();
        mFillTexturePaint.reset();
        mFillTexturePaint.setStyle(Style.FILL);

        mEdgeDashPathPaint.reset();
        mEdgeDashPathPaint.setPathEffect(mEffect);
        mEdgeDashPathPaint.setStyle(Style.STROKE);
        mEdgeDashPathPaint.setStrokeWidth(4);

        mEdgeUpPathPaint.reset();
        mEdgeUpPathPaint.setStyle(Style.STROKE);
        mEdgeUpPathPaint.setStrokeWidth(4);
        mEdgeUpPathPaint.setColor(Color.WHITE);

        mTextureShader = texture;
    }

    public void initCut() {
        if (mInCut) {
            return;
        }
        if (mRectRegion == null) {
            mRectRegion = new Region();
        }
        if (mChoiceRegion == null) {
            mChoiceRegion = new Region();
        }
        if (mTextureOrCutCanvas == null) {
            mTextureOrCutCanvas = new Canvas();
        }
        if (mCutPathRect == null) {
            mCutPathRect = new RectF();
        }
        if (mCutOriginRect == null) {
            mCutOriginRect = new RectF();
        }

        mInCut = true;
        mEdgePath.reset();
        mCutEdgePath.reset();

        mCutPaint.setColor(Color.RED);
        mCutPaint.setStyle(Style.FILL);

        mCutFillPaint.setColor(mBgColor);
        mCutFillPaint.setStyle(Style.FILL);

        mEdgeDashPathPaint.reset();
        mEdgeDashPathPaint.setStyle(Style.STROKE);
        mEdgeDashPathPaint.setPathEffect(mEffect);
        mEdgeDashPathPaint.setStrokeWidth(4);

        mEdgeUpPathPaint.reset();
        mEdgeUpPathPaint.setStyle(Style.STROKE);
        mEdgeUpPathPaint.setStrokeWidth(4);
        mEdgeUpPathPaint.setColor(Color.WHITE);
    }

    public void outCut() {
        mInCut = false;
    }

    public void outFillTexture() {
        mInFillTexture = false;
    }

    public void commitCut() {
        if (mInCutCommit && mInCut) {
            mInCutCommit = false;
            commitBitmap(COMMIT_CUT_BITMAP);
            invalidate();
        }
    }

    public void commitTexture() {
        if (mFillTextureCommit && mInFillTexture) {
            mFillTextureCommit = false;
            commitBitmap(COMMIT_FILL_TEXTURE);
            invalidate();
        }
    }

    public void commitInsert() {
        if (mInInsertImage) {
            mInInsertImage = false;
            commitBitmap(COMMIT_INSERT_PICTURE);
            mPaintStateListener.onInsertFinish();
            invalidate();
        }
    }

    public void setTextureShader(Shader shader) {
        if (mInFillTexture && shader != null) {
            mTextureShader = shader;
        }
    }

    public void clear() {
        if (mBrush != null) {
            mBrush.clear();
        }
    }
}
